#include "EditorContextMenuViewModel.h"

#include "Asset/EditorDefaultPackage.h"


#include "Widgets/Widget.h"
#include "Widgets/Window.h"

#include "Platform/PlatformInterface.h"

REditorContextMenuViewModel::REditorContextMenuViewModel()
{
    ExpandIcon= REditorDefaultPackage::Get()->GetIcon(U"CodiconChevronRight");
}

void REditorContextMenuViewModel::SetRootItem(
    const TVector<SEditorContextMenuLine> &InRootItems
    , SIntPoint InRootMenuScreenPosition
    , float InRootMenuWidth
    , TDelegate<void> InOnContextMenuClosed
    )
{
    Clear();

    OnContextMenuClosed=InOnContextMenuClosed;

    assert(InRootItems.Num()>0);
    RootMenuLines = InRootItems;
    RootMenuScreenPosition = InRootMenuScreenPosition;
    RootMenuWidth = InRootMenuWidth;

    auto& FirstMenu=MenuDatas.Emplace();
    FirstMenu.SelectedLine=-1;
    FirstMenu.Width=RootMenuWidth;
    FirstMenu.Lines=RootMenuLines;
    FirstMenu.ScreenPosition=RootMenuScreenPosition;

    OnVisibleMenuChanged.ExecuteIfBound(0);

    ElapsedTime=0;
    HoveredMenuIndex=-1;
    HovoredLineIndex=-1;
}

void REditorContextMenuViewModel::SetRootItem(const TVector<SEditorContextMenuLine> &InRootItems, TSharedObjectPtr<RWidget> InAroundWidget, float InRootMenuWidth)
{
    float Height=0;
    for(auto& Line:InRootItems)
    {
        if(Line.bIsSeparator)
        {
            Height+=SepreatorLineHeight;
        }
        else
        {
            Height+=NormalLineHeight;
        }
    }

    auto WidgetRect=InAroundWidget->GetRectInScreenSpace(); 

    auto WorkArea= InAroundWidget->GetWindow()->GetPlacebleArea();

    SIntPoint ContextMenuPosition= 
        SIntPoint(
            WidgetRect.Min.X,
            WidgetRect.Min.Y+ WidgetRect.Extent.Y
    );

    if(ContextMenuPosition.X+InRootMenuWidth>WorkArea.Min.X+WorkArea.Extent.X)
    {
        ContextMenuPosition.X=WorkArea.Min.X+WorkArea.Extent.X-InRootMenuWidth;
    }
    else if(ContextMenuPosition.X<WorkArea.Min.X)
    {
        ContextMenuPosition.X=WorkArea.Min.X;
    }


    if(ContextMenuPosition.Y+Height>WorkArea.Min.Y+WorkArea.Extent.Y)
    {
        //try to show above
        ContextMenuPosition.Y=WidgetRect.Min.Y-Height;
        if(ContextMenuPosition.Y<WorkArea.Min.Y)
        {
            //above space is not enough
            //try to show below
            ContextMenuPosition.Y=WorkArea.Min.Y+WorkArea.Extent.Y-Height;
        }
    }
    else if(ContextMenuPosition.Y<WorkArea.Min.Y)
    {
        ContextMenuPosition.Y=WorkArea.Min.Y;
    }

    SetRootItem(InRootItems,ContextMenuPosition,InRootMenuWidth);

}

bool REditorContextMenuViewModel::IsEmpty()
{
    return MenuDatas.Empty();
}

void REditorContextMenuViewModel::Clear()
{
    int64_t MenuNum=MenuDatas.Num();
    MenuDatas.Clear();

    for(int64_t i=0;i<MenuNum;++i)
    {
        OnVisibleMenuChanged.ExecuteIfBound(i);
    }

    OnContextMenuClosed.ExecuteIfBound();
    OnContextMenuClosed.Unbind();
}

void REditorContextMenuViewModel::OnLostFocus()
{
    Clear();
}

bool REditorContextMenuViewModel::IsMenuEmpty(int64_t MenuIndex) const
{
    return MenuIndex >= MenuDatas.Num();
}

float REditorContextMenuViewModel::GetMenuWidth(int64_t InMenuIndex) const
{
    return MenuDatas[InMenuIndex].Width;
}

int64_t REditorContextMenuViewModel::GetMenuHeight(int64_t InMenuIndex) const
{
    //calculate height
    float Sum=0;
    for(auto& Line:MenuDatas[InMenuIndex].Lines)
    {
        if(Line.bIsSeparator)
        {
            Sum+=SepreatorLineHeight;
        }
        else
        {
            Sum+=NormalLineHeight;
        }
    }

    return Sum;
}

SIntPoint REditorContextMenuViewModel::GetMenuScreenPosition(int64_t InMenuIndex) const
{
    return MenuDatas[InMenuIndex].ScreenPosition;
}

int64_t REditorContextMenuViewModel::GetMenuLineNum(int64_t InMenuIndex) const
{
    return MenuDatas[InMenuIndex].Lines.Num();
}

TSharedObjectPtr<RTexture2D> REditorContextMenuViewModel::GetExpandIcon() const
{
    return ExpandIcon;
}

TSharedObjectPtr<RTexture2D> REditorContextMenuViewModel::GetIcon(int64_t InMenuIndex, int64_t InLineIndex) const
{
    return MenuDatas[InMenuIndex].Lines[InLineIndex].Icon;
}

CString REditorContextMenuViewModel::GetText(int64_t InMenuIndex, int64_t InLineIndex) const
{
    return MenuDatas[InMenuIndex].Lines[InLineIndex].Text;
}

CString REditorContextMenuViewModel::GetShorcutText(int64_t InMenuIndex, int64_t InLineIndex) const
{
    return MenuDatas[InMenuIndex].Lines[InLineIndex].ShorcutText;
}

bool REditorContextMenuViewModel::IsExpandIconVisible(int64_t InMenuIndex, int64_t InLineIndex) const
{
    return !MenuDatas[InMenuIndex].Lines[InLineIndex].SubMenus.Empty();
}

bool REditorContextMenuViewModel::IsSeparator(int64_t InMenuIndex, int64_t InLineIndex) const
{
    return MenuDatas[InMenuIndex].Lines[InLineIndex].bIsSeparator;
}

bool REditorContextMenuViewModel::IsValid(int64_t InMenuIndex, int64_t InLineIndex) const
{
    return MenuDatas[InMenuIndex].Lines[InLineIndex].bIsValid;
}

void REditorContextMenuViewModel::OnHovered(int64_t InMenuIndex, int64_t InLineIndex)
{    
    HoveredMenuIndex= InMenuIndex; 
    HovoredLineIndex= InLineIndex;
    
    ElapsedTime=0;
}

void REditorContextMenuViewModel::OnUnHovered(int64_t InMenuIndex, int64_t InLineIndex)
{
    //different menu is in different window
    //unhovor might invoked after hover
    if(InMenuIndex==HoveredMenuIndex
        && HovoredLineIndex==InLineIndex
    )
    {
        HoveredMenuIndex=-1;
        HovoredLineIndex=-1;

        ElapsedTime=0;
    }
}

void REditorContextMenuViewModel::OnClicked(int64_t InMenuIndex, int64_t InLineIndex)
{
    if(!MenuDatas[InMenuIndex].Lines[InLineIndex].OnClicked.IsBound())
    {
        return ;
    }
    MenuDatas[InMenuIndex].Lines[InLineIndex].OnClicked.Execute();

    Clear();
}

bool REditorContextMenuViewModel::ShouldHighlight(int64_t InMenuIndex) const
{
    if(InMenuIndex>=MenuDatas.Num())
    {
        return false;
    }

    if(InMenuIndex==HoveredMenuIndex)
    {
        return true;
    }

    if(MenuDatas[InMenuIndex].SelectedLine!=-1)
    {
        return true;
    }

    return false;
}

int64_t REditorContextMenuViewModel::GetHighlightLineIndex(int64_t InMenuIndex) const
{
    if(InMenuIndex>=MenuDatas.Num())
    {
        return -1;
    }

    if(InMenuIndex==HoveredMenuIndex)
    {
        return HovoredLineIndex;
    }

    if(MenuDatas[InMenuIndex].SelectedLine!=-1)
    {
        return MenuDatas[InMenuIndex].SelectedLine;
    }

    return -1;
}

void REditorContextMenuViewModel::Tick(float DeltaSeconds)
{
    if(MenuDatas.Empty())
    {
        return ;
    }

    ElapsedTime+=DeltaSeconds;

    if(ElapsedTime<=0.5f)
    {
        return;
    }

    if(HoveredMenuIndex !=-1)
    {
        //bool MenuChanged=false;
        //remove sub menu after next menu
        while(MenuDatas.Num()>HoveredMenuIndex+2)
        {
            MenuDatas.Pop();

            OnVisibleMenuChanged.ExecuteIfBound(MenuDatas.Num());
        }

        //try remove next menu
        if(MenuDatas.Num()>HoveredMenuIndex+1
            && MenuDatas[HoveredMenuIndex+1].LineInParent !=HovoredLineIndex
        )
        {
            MenuDatas.Pop();

            OnVisibleMenuChanged.ExecuteIfBound(MenuDatas.Num());
        }

        MenuDatas.Last().SelectedLine=-1;

        //try show new sub menu
        if(MenuDatas.Num() ==HoveredMenuIndex+1
            &&!MenuDatas[HoveredMenuIndex].Lines[HovoredLineIndex].SubMenus.Empty())
        {
            auto& SubMenu=MenuDatas.Emplace();
            auto& ParentMenu=MenuDatas[HoveredMenuIndex];
            SubMenu.SelectedLine=-1;
            SubMenu.Width=ParentMenu.Lines[HovoredLineIndex].SubMenuWidth;
            SubMenu.Lines=ParentMenu.Lines[HovoredLineIndex].SubMenus;
            SubMenu.LineInParent=HovoredLineIndex;
            ParentMenu.SelectedLine=HovoredLineIndex;
            {
                SubMenu.ScreenPosition=ParentMenu.ScreenPosition+ SIntPoint(ParentMenu.Width,0);

                for(int ParentMenuLineIndex=0;
                    ParentMenuLineIndex<SubMenu.LineInParent;
                    ++ParentMenuLineIndex
                )
                {
                    if(ParentMenu.Lines[ParentMenuLineIndex].bIsSeparator)
                    {
                        SubMenu.ScreenPosition.Y+=SepreatorLineHeight;
                    }
                    else
                    {
                        SubMenu.ScreenPosition.Y+=NormalLineHeight;
                    }
                }

                auto WorkArea=IPlatformInterface::GetPlatformInterface()
                    ->GetDisplayWorkArea(MenuDatas[0].ScreenPosition);
                    
                //check if sub menu is out of screen
                if(SubMenu.ScreenPosition.X+SubMenu.Width>WorkArea.Min.X+WorkArea.Extent.X)
                {
                    //try to show left of parent
                    int64_t XAtLeft=ParentMenu.ScreenPosition.X-SubMenu.Width;
                    if(XAtLeft> WorkArea.Min.X)
                    {
                        SubMenu.ScreenPosition.X=XAtLeft;
                    }
                }

                int64_t Height=GetMenuHeight(HoveredMenuIndex+1);
                if(SubMenu.ScreenPosition.Y+Height>WorkArea.Min.Y+WorkArea.Extent.Y)
                {
                    //try to make little higher
                    SubMenu.ScreenPosition.Y=WorkArea.Min.Y+WorkArea.Extent.Y-Height;
                }
            
            }


            OnVisibleMenuChanged.ExecuteIfBound(MenuDatas.Num()-1);
        }

    }
    else
    {
        //try remove all menu except first
        //bool MenuChanged=false;

        while(MenuDatas.Num()>1)
        {
            MenuDatas.Pop();
            OnVisibleMenuChanged.ExecuteIfBound(MenuDatas.Num());
        }

        MenuDatas[0].SelectedLine=-1;

        ElapsedTime=0;
    }
}
